package com.sdnc.common.json;

import com.sdnc.common.constant.Header;
import org.noear.snack.ONode;
import org.noear.snack.core.Feature;
import org.noear.snack.core.Options;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.util.StreamUtils;

import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;

/**
 * 自定义SpringBoot JSON转换器
 */
public final class SnackHttpMessageConverter<T> implements HttpMessageConverter<T> {

	@Override
	public boolean canRead(Class<?> clazz, MediaType mediaType) {
		return true;
	}

	@Override
	public boolean canWrite(Class<?> clazz, MediaType mediaType) {
		return true;
	}

	@Override
	public List<MediaType> getSupportedMediaTypes() {
		return List.of(MediaType.APPLICATION_JSON);
	}

	@Override
	public T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException {
		String json = StreamUtils.copyToString(inputMessage.getBody(), StandardCharsets.UTF_8);

		return ONode.deserialize(json, clazz);
	}

	@Override
	public void write(T obj, MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException {
		if (outputMessage.getHeaders().isEmpty()) {
			outputMessage.getHeaders().add(Header.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
		}
		try (OutputStream output = outputMessage.getBody()) {
			Options options = Options.of(Feature.QuoteFieldNames, Feature.WriteDateUseFormat);
			options.addEncoder(BigInteger.class, (data, node) -> node.val().setString(data.toString()));
			options.addEncoder(BigDecimal.class, (data, node) -> node.val().setString(data.toPlainString()));
			options.addEncoder(LocalDate.class, (data, node) -> node.val().setString(String.format("%tF", data)));
			options.addEncoder(LocalDateTime.class, (data, node) -> node.val().setString(String.format("%1$tF %1$tT", data)));
			byte[] bytes = ONode.stringify(obj, options).getBytes(StandardCharsets.UTF_8);
			for (int i = 0, size = bytes.length; i < size; ++i) {
				output.write(bytes[i]);
			}
			output.flush();
		}
	}

}
